package com.mpsc.current.unit;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.geotools.data.*;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.referencing.CRS;
import org.json.JSONException;
import org.json.JSONObject;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;

@Slf4j
public class GeoToolsUtil {

    @Getter
    @AllArgsConstructor
    public enum GeometryType {

        POINT("Point", "Point", Point.class),
        MULTI_POINT("MultiPoint", "MultiPoint", MultiPoint.class),
        LINE_STRING("LineString", "Link", LineString.class),
        MULTI_LINE_STRING("MultiLineString", "MultiLineString", MultiLineString.class),
        POLYGON("Polygon", "Polygon", Polygon.class),
        MULTI_POLYGON("MultiPolygon", "MultiPolygon", MultiPolygon.class);

        private String geometryType;

        private String typeName;

        private Class clazz;

        public static GeometryType parseOfGeometryType(String geometryType) {
            for (GeometryType item : values()) {
                if (item.getGeometryType().equalsIgnoreCase(geometryType)) {
                    return item;
                }
            }
            throw new IllegalArgumentException("Geometry Type [" + geometryType + "] 不存在!");
        }

    }
    
    /**
     * 默认经度
     */
    private static final Integer DEFAULT_DECIMALS = 15;

    private volatile static GeoToolsUtil instance;

    private GeometryFactory geometryFactory = null;

    private Geometry geometry = null;

    private static CoordinateReferenceSystem DEFAULT_CRS = null;

    private static Charset DEFAULT_SHAPE_FILE_CHARSET = Charset.forName("GBK");

    static {
        try {
            DEFAULT_CRS = CRS.decode("EPSG:4490");
        } catch (FactoryException e) {
            e.printStackTrace();
        }
    }

    private GeoToolsUtil() {
        geometryFactory = JTSFactoryFinder.getGeometryFactory();
    }

    /**
     * 是否包含中文字符
     * @param strName
     * @return
     */
    public static boolean isChinese(String strName) {
        char[] ch = strName.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            char c = ch[i];
            if (isChinese(c)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否包含中文字符
     * @param c
     * @return
     */
    private static final boolean isChinese(char c) {
        Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(c);
        return unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
                || unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
                || unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
                || unicodeBlock == Character.UnicodeBlock.GENERAL_PUNCTUATION
                || unicodeBlock == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
                || unicodeBlock == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS;
    }

    // 链式条用，涉及线程安全，使用懒汉式单例
    public static GeoToolsUtil getInstance() {
        return new GeoToolsUtil();
    }

    /**
     * 获取 空间参考系统ID
     *
     * @return
     */
    public int getSRID() {
        return geometry.getSRID();
    }

    /**
     * 获取 Geometry 实际类型
     *
     * @return
     */
    public String getGeometryType() {
        return geometry.getGeometryType();
    }

    /**
     * 获取精度
     *
     * @return
     */
    public PrecisionModel getPrecisionModel() {
        return geometry.getPrecisionModel();
    }

    /**
     * 获取 用户数据对象（如果有）
     * 旨在供开发人员使用，最佳实践是存储 java.util.Map。
     * GeoTools 偶尔会使用此字段来存储 srsName 或完整的 CoordinateReferenceSystem。
     *
     * @return
     */
    public Object getUserData() {
        return geometry.getUserData();
    }

    /**
     * 获取维度
     *
     * @return
     */
    public int getDimension() {
        return geometry.getDimension();
    }

    /**
     * 获取中心点
     *
     * @return
     */
    public Point getCenterPoint() {
        List<Coordinate> coordinateList = Arrays.asList(geometry.getEnvelope().getCoordinates());
        double xMin = coordinateList.stream().mapToDouble(item -> item.getX()).min().getAsDouble();
        double xMax = coordinateList.stream().mapToDouble(item -> item.getX()).max().getAsDouble();
        double yMin = coordinateList.stream().mapToDouble(item -> item.getY()).min().getAsDouble();
        double yMax = coordinateList.stream().mapToDouble(item -> item.getY()).max().getAsDouble();
        return JTSFactoryFinder.getGeometryFactory().createPoint(new Coordinate(xMin + (xMax - xMin) / 2, yMin + (yMax - yMin) / 2));
    }

    /**
     * 获取重心点(不一定在几何体内)
     *
     * @return
     */
    public Point getGravityPoint() {
        double area = 0.0, x = 0.0, y = 0.0;
        List<Coordinate> coordinateList = Arrays.asList(geometry.getCoordinates());
        for (int i = 1; i < coordinateList.size(); i++) {
            double lat = coordinateList.get(i % coordinateList.size()).x;
            double lng = coordinateList.get(i % coordinateList.size()).y;
            double nextLat = coordinateList.get(i - 1).x;
            double nextLng = coordinateList.get(i - 1).y;
            double temp = (lat * nextLng - lng * nextLat) / 2.0;
            area += temp;
            x += temp * (lat + nextLat) / 3.0;
            y += temp * (lng + nextLng) / 3.0;
        }
        return JTSFactoryFinder.getGeometryFactory().createPoint(new Coordinate(x / area, y / area));
    }

    /**
     * 获取质心
     *
     * @return
     */
    public Point getCentroidPoint() {
        return geometry.getCentroid();
    }

    /**
     * 获取几何体的中心(在几何体内)
     *
     * @return
     */
    public Point getInteriorPoint() {
        return geometry.getInteriorPoint();
    }

    /**
     * 获取边界
     * 几何的边界是一组下一个较低维度的几何。
     *
     * @return
     */
    public Geometry getBoundary() {
        return geometry.getBoundary();
    }

    /**
     * 获取四至
     *
     * @return
     */
    public Geometry getEnvelope() {
        return geometry.getEnvelope();
    }

    /**
     * 获取四至
     *
     * @return
     */
    public Envelope getEnvelopeInternal() {
        return geometry.getEnvelopeInternal();
    }

    /**
     * 获取 长度/周长
     *
     * @return
     */
    public double getLength() {
        return geometry.getLength();
    }

    /**
     * 获取 面积
     *
     * @return
     */
    public double getArea() {
        return geometry.getArea();
    }

    /**
     * 坐标系转换
     *
     * @param sourceCode
     * @param targetCode
     * @return
     * @throws Exception
     */
    public Geometry transformCoordinates(String sourceCode, String targetCode) throws Exception {
        // 设置经纬度顺序
        CoordinateReferenceSystem sourceCRS = CRS.decode(sourceCode, true);
        CoordinateReferenceSystem targetCRS = CRS.decode(targetCode, true);
        // 投影转换
        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        return JTS.transform(geometry, transform);
    }

    /**
     * 导入 Geometry 数据
     *
     * @param geometry
     * @return
     */
    public GeoToolsUtil fromGeometry(Geometry geometry) {
        this.geometry = geometry;
        return this;
    }

    /**
     * 导出 Geometry 数据
     *
     * @return
     */
    public Geometry toGeometry() {
        return this.geometry;
    }

    /**
     * 导入 GeoJson 数据
     *
     * @param geoJson
     * @return
     * @throws IOException
     */
//    public GeoToolsUtil fromGeoJson(final String geoJson) throws IOException {
//        return this.fromGeoJson(geoJson, DEFAULT_DECIMALS);
//    }

    /**
     * 导入 GeoJson 数据
     *
     * @param geoJson
     * @return
     * @throws IOException
     */
//    public GeoToolsUtil fromGeoJson(final String geoJson, int decimals) throws IOException {
//        GeometryJSON geometryJson = new GeometryJSON(decimals);
//        try {
//            JSONObject geometry = new JSONObject(geoJson).getJSONObject("geometry");
//            this.geometry = geometryJson.read(geometry == null ? geoJson : new StringReader(geometry.toString()));
//        } catch (JSONException e) {
//            e.printStackTrace();
//        }
//        return this;
//    }

    /**
     * 导出 GeoJson 数据
     * 设置保留6位小数，否则 GeometryJSON 默认保留4位小数
     *
     * @return
     * @throws IOException
     */
//    public StringWriter toGeoJSON() throws IOException {
//        return toGeoJSON(DEFAULT_DECIMALS);
//    }

    /**
     * 导出 GeoJson 数据
     *
     * @param decimals
     * @return
     * @throws IOException
     */
//    public StringWriter toGeoJSON(int decimals) throws IOException {
//        StringWriter writer = new StringWriter();
//        GeometryJSON geometryJson = new GeometryJSON(decimals);
//        geometryJson.write(geometry, writer);
//        return writer;
//    }

    /**
     * 导出 Feature GeoJson 数据
     *
     * @return
     */
//    public String toFeatureGeoJSON() {
//        return this.toFeatureGeoJSON(null);
//    }

    /**
     * 导出 Feature GeoJson 数据
     *
     * @param properties
     * @return
     */
//    public String toFeatureGeoJSON(Map<String, Object> properties) {
//        GeometryType geometryType = GeometryType.parseOfGeometryType(geometry.getGeometryType());
//        // 第一个位置添加 geometryType
//        Map<String, Object> defaultProperties = new LinkedHashMap<>(properties == null ? 1 : properties.size() + 1);
//        defaultProperties.put("geometry", geometryType.getGeometryType());
//        if (properties != null) {
//            defaultProperties.putAll(properties);
//        }
//
//        // 构建 typeSpec
//        StringJoiner typeSpec = new StringJoiner(",");
//        for (Map.Entry<String, Object> property : defaultProperties.entrySet()) {
//            if ("geometry".equals(property.getKey())) {
//                typeSpec.add(property.getKey() + ":" + geometryType.getGeometryType());
//                continue;
//            }
//            typeSpec.add(property.getKey() + ":" + property.getValue().getClass().getSimpleName());
//        }
//
//        StringWriter writer = new StringWriter();
//        try {
//            // 属性赋值
//            SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(DataUtilities.createType(geometryType.getTypeName(), typeSpec.toString()));
//            // 按照type中声明的顺序为属性赋值就可以
//            for (Map.Entry<String, Object> property : defaultProperties.entrySet()) {
//                if ("geometry".equals(property.getKey())) {
//                    featureBuilder.add(geometry);
//                    continue;
//                }
//                featureBuilder.add(property.getValue());
//            }
//            SimpleFeature feature = featureBuilder.buildFeature("");
//
//            FeatureJSON featureJSON = new FeatureJSON();
//            featureJSON.writeFeature(feature, writer);
//        } catch (Exception e) {
//
//        }
//        return writer == null ? null : writer.toString().replace(",\"id\":\"\"", "");
//    }

    /**
     * 导入 WKT (Well-known Text) 数据
     *
     * @param wkt
     * @return
     * @throws ParseException
     */
    public GeoToolsUtil fromWkt(String wkt) throws ParseException {
        WKTReader reader = new WKTReader(geometryFactory);
        this.geometry = reader.read(wkt);
        return this;
    }

    /**
     * 导出 WKT 数据
     *
     * @return
     */
    public String toWkt() {
        return geometry.toText();
    }

    /**
     * 导入 ShapeFile 文件
     *
     * @param shapeFile
     * @return
     */
    public static List<Geometry> fromShapeFile(File shapeFile) {
        List<Geometry> geometryList = new ArrayList<>();
        try {
            ShapefileDataStore dataStore = new ShapefileDataStore(shapeFile.toURI().toURL());

            SimpleFeatureType featureType = dataStore.getSchema();
            CoordinateReferenceSystem crs = featureType.getCoordinateReferenceSystem();
            if (crs.toWKT().startsWith(("PROJCS"))) {
                log.error("该数据坐标系为投影坐标系！");
            } else if (crs.toWKT().startsWith(("GEOGCS"))) {
                if (crs.toWKT().contains("CGCS2000") || crs.toWKT().contains("2000")) {
                    log.debug("该数据坐标系是EPSG:4490.");
                } else {
                    log.error("该数据坐标系为投影坐标系!");
                }
            }

            FeatureSource<SimpleFeatureType, SimpleFeature> featureSource = dataStore.getFeatureSource(dataStore.getTypeNames()[0]);
            FeatureIterator<SimpleFeature> featureIterator = featureSource.getFeatures().features();
            while (featureIterator.hasNext()) {
                SimpleFeature feature = featureIterator.next();
                geometryList.add((Geometry) feature.getAttribute("the_geom"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return geometryList;
    }

    /**
     * 构建 FeatureType
     * @param handledGeoJsonList
     * @param feature0
     * @return
     * @throws FactoryException
     */
    public static SimpleFeatureType buildFeatureType(List<String> handledGeoJsonList, com.alibaba.fastjson.JSONObject feature0) throws FactoryException {
        // 定义图形信息和属性信息
        SimpleFeatureTypeBuilder featureTypeBuilder = new SimpleFeatureTypeBuilder();
        featureTypeBuilder.setCRS(DEFAULT_CRS);
        featureTypeBuilder.setName("shapefile");

        // 若为 features 则向下查找
        if (feature0.containsKey("features")) {
            feature0 = (com.alibaba.fastjson.JSONObject) feature0.getJSONArray("features").get(0);
        }

        // 移除 crs
        com.alibaba.fastjson.JSONObject geometry = feature0.getJSONObject("geometry");
        if (geometry.containsKey("crs")) {
            geometry.remove("crs");
        }

        if (geometry.containsKey("type")) {
            GeometryType type = GeometryType.parseOfGeometryType(geometry.getString("type"));
            featureTypeBuilder.add("the_geom", type.getClazz());
        }

        if (feature0.containsKey("properties")) {
            com.alibaba.fastjson.JSONObject properties0 = feature0.getJSONObject("properties");

            com.alibaba.fastjson.JSONObject handledProperties = new com.alibaba.fastjson.JSONObject();
            for (String key : properties0.keySet()) {
                if (!"geometry".equals(key)) {
                    continue;
                }
                Object value = properties0.get(key);
                if (isChinese(key) && key.length() > 3) {
                    key = key.substring(0, 3);
                } else if (key.length() > 10) {
                    key = key.substring(0, 10);
                }
                handledProperties.put(key, value);

                if (value instanceof Integer) {
                    featureTypeBuilder.add(key, Integer.class);
                } else if (value instanceof Double) {
                    featureTypeBuilder.add(key, Double.class);
                } else {
                    featureTypeBuilder.add(key, String.class);
                }
            }
            feature0.put("properties", handledProperties);

            handledGeoJsonList.add(feature0.toString());
        }
        return featureTypeBuilder.buildFeatureType();
    }

    /**
     * 写入数据
     * @param geoJson
     * @param writer
     * @throws IOException
     */
//    private static void writerFeature(com.alibaba.fastjson.JSONObject geoJson, FeatureWriter<SimpleFeatureType, SimpleFeature> writer) throws IOException {
//        SimpleFeature feature = writer.next();
//
//        // 属性数据
//        com.alibaba.fastjson.JSONObject properties = geoJson.getJSONObject("properties");
//        for (String key : properties.keySet()) {
//            if (!"geometry".equals(key) && !"operation".equals(key) && !"layerName".equals(key) && !"no".equals(key)) {
//                Object propertyValue = properties.get(key);
//                if (propertyValue instanceof String) {
//                    String value = propertyValue.toString();
//                    if (isChinese(value) && value.length() > 84) {
//                        propertyValue = value.substring(0, 84);
//                    }
//                }
//                if (isChinese(key) && key.length() > 3) {
//                    key = key.substring(0, 3);
//                } else if (key.length() > 10) {
//                    key = key.substring(0, 10);
//                }
//                feature.setAttribute(key, propertyValue);
//            }
//        }
//
//        // 空间数据
//        GeometryJSON geometryJSON = new GeometryJSON(15);
//        Geometry geometry = geometryJSON.read(new StringReader(geoJson.getString("geometry")));
//        feature.setAttribute("the_geom", geometry);
//        writer.write();
//    }

    /**
     * 导出 ShapeFile 文件
     *
     * @param geoJsonList geoJson 字符串 列表
     * @param savePath    保存目录
     * @return
     */
//    public static File toShapeFile(List<String> geoJsonList, final String savePath) {
//        String tempPath = savePath;
//        if (tempPath.endsWith(".shp") || tempPath.endsWith(".zip")) {
//            tempPath = tempPath.replaceAll(".shp", "").replaceAll(".zip", "");
//        }
//        if (tempPath.endsWith("/")) {
//            tempPath = tempPath.substring(0, tempPath.length() - 1);
//        }
//
//        int indexSlant = tempPath.lastIndexOf("/");
//        String fileName = tempPath.substring(indexSlant + 1);
//
//        // 存储目录
//        File storePath = new File(tempPath);
//        if (!storePath.exists()) {
//            log.info("存在老数据，执行清理.");
//            storePath.mkdirs();
//        }
//
//        // 存储的 shape 文件
//        File storeFile = new File(tempPath + "/" + fileName + ".shp");
//        if (storeFile.exists()) {
//            log.info("存在老数据，执行清理.");
//            storePath.list((dir, name) -> name.startsWith(fileName) && new File(dir.getPath() + "/" + name).delete());
//        }
//
//        try {
//            Map<String, Serializable> params = new HashMap<>(1);
//            params.put(ShapefileDataStoreFactory.URLP.key, storeFile.toURI().toURL());
//            FileDataStore dataStore = FileDataStoreFinder.getDataStore(storeFile);
//
//            // 设置编码
//            ((ShapefileDataStore) dataStore).setCharset(DEFAULT_SHAPE_FILE_CHARSET);
//            ShapefileDataStore shapefileDataStore = (ShapefileDataStore) new ShapefileDataStoreFactory().createNewDataStore(params);
//            shapefileDataStore.setCharset(DEFAULT_SHAPE_FILE_CHARSET);
//
//            // 属性设置
//            List<String> handledList = new ArrayList<>();
//            for (String geoJson : geoJsonList) {
//                dataStore.createSchema(buildFeatureType(handledList, com.alibaba.fastjson.JSONObject.parseObject(geoJson)));
//            }
//
//            // 写入数据
//            FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore.getFeatureWriter(dataStore.getTypeNames()[0], Transaction.AUTO_COMMIT);
//            for (String geoJson : handledList) {
//                com.alibaba.fastjson.JSONObject tempJson = com.alibaba.fastjson.JSONObject.parseObject(geoJson);
//                writerFeature(tempJson, writer);
//            }
//            writer.close();
//            shapefileDataStore.dispose();
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        return storePath;
//    }

    // --- 几何关系 ---

    /**
     * 是否包含
     *
     * @param otherGeometry
     * @return
     */
    public boolean contains(Geometry otherGeometry) {
        return this.geometry.contains(otherGeometry);
    }

    /**
     * 是否在内部
     *
     * @param otherGeometry
     * @return
     */
    public boolean within(Geometry otherGeometry) {
        return this.geometry.within(otherGeometry);
    }

    /**
     * 是否相交
     *
     * @param otherGeometry
     * @return
     */
    public boolean crosses(Geometry otherGeometry) {
        return this.geometry.crosses(otherGeometry);
    }

    /**
     * 是否相交
     *
     * @param otherGeometry
     * @return
     */
    public boolean intersects(Geometry otherGeometry) {
        return this.geometry.intersects(otherGeometry);
    }

    /**
     * 是否不相交
     *
     * @param otherGeometry
     * @return
     */
    public boolean disjoint(Geometry otherGeometry) {
        return this.geometry.disjoint(otherGeometry);
    }

    /**
     * 是否相等
     *
     * @param otherGeometry
     * @return
     */
    public boolean equals(Geometry otherGeometry) {
        return this.geometry.equals(otherGeometry);
    }

    /**
     * 是否完全相等
     *
     * @param otherGeometry
     * @return
     */
    public boolean equalsExact(Geometry otherGeometry) {
        return this.geometry.equalsExact(otherGeometry);
    }

    /**
     * 是否重叠
     *
     * @param otherGeometry
     * @return
     */
    public boolean overlaps(Geometry otherGeometry) {
        return this.geometry.overlaps(otherGeometry);
    }

    /**
     * 是否覆盖
     *
     * @param otherGeometry
     * @return
     */
    public boolean covers(Geometry otherGeometry) {
        return this.geometry.covers(otherGeometry);
    }

    /**
     * 是否被覆盖
     *
     * @param otherGeometry
     * @return
     */
    public boolean coveredBy(Geometry otherGeometry) {
        return this.geometry.coveredBy(otherGeometry);
    }

    /**
     * 是否接触
     *
     * @param otherGeometry
     * @return
     */
    public boolean touches(Geometry otherGeometry) {
        return this.geometry.touches(otherGeometry);
    }

    /**
     * 获取 DE-9IM 关系表达式
     *
     * @param otherGeometry
     * @return
     */
    public IntersectionMatrix relate(Geometry otherGeometry) {
        return this.geometry.relate(otherGeometry);
    }

    /**
     * 验证 DE-9IM 关系表达式
     *
     * @param otherGeometry
     * @param pattern
     * @return
     */
    public boolean relate(Geometry otherGeometry, String pattern) {
        return this.geometry.relate(otherGeometry, pattern);
    }

    // --- 几何操作 ---

    /**
     * 获取 距离
     *
     * @param otherGeometry
     * @return
     */
    public double distance(Geometry otherGeometry) {
        return this.geometry.distance(otherGeometry);
    }

    /**
     * 获取 缓冲几何体
     *
     * @param distance
     * @return
     */
    public Geometry getBuffer(double distance) {
        return this.geometry.buffer(distance);
    }

    /**
     * 获取 相交的几何体
     *
     * @param otherGeometry
     * @return
     */
    public Geometry intersection(Geometry otherGeometry) {
        return this.geometry.intersection(otherGeometry);
    }

    /**
     * 获取 合并的几何体
     *
     * @param otherGeometry
     * @return
     */
    public Geometry union(Geometry otherGeometry) {
        return this.geometry.union(otherGeometry);
    }

    /**
     * 获取 差异的几何体
     *
     * @param otherGeometry
     * @return
     */
    public Geometry difference(Geometry otherGeometry) {
        return this.geometry.difference(otherGeometry);
    }

    /**
     * 获取 符号差异的几何体
     *
     * @param otherGeometry
     * @return
     */
    public Geometry symDifference(Geometry otherGeometry) {
        return this.geometry.symDifference(otherGeometry);
    }

}
